//
// UTF8.cpp
// libprotea
// Tools Library for the Protea Project / UTF-8 support
// Copyright (C) 2003, 2004, 2005 eXegeSys, Inc.
// Copyright (C) 2008 Bruce A. James
//
// This program is part of the Protea Project.
//
// The Protea Project is free software; you can redistribute it and/or modify it 
// under the terms of the GNU General Public License as published by the 
// Free Software Foundation, either version 3 of the License, or (at your 
// option) any later version.
//
// The Protea Project is distributed in the hope that it will be useful, but 
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 
// for more details.
// 
// You should have received a copy of the GNU General Public License along 
// with this program.  If not, see <http://www.gnu.org/licenses/>.
// 
// Please send correspondence to:
// 
// theproteaproject@gmail.com
//

#include <string.h>
#include <stdio.h>

#include <xercesc/util/PlatformUtils.hpp>
#include <xercesc/util/TransService.hpp>
#include <xercesc/util/XMLString.hpp>

using namespace XERCES_CPP_NAMESPACE;

#include "../defines.h"
#include "../AppHandler/AppInterface.h"
#include "utf8.h"



//
// Generic unicode parsing routines
//
XCUNIPARSE::XCUNIPARSE() {
	univalue = 0;
	unichars = 0;
};

void XCUNIPARSE::Reset() {
	univalue = 0;
};


void XCUNIPARSE::AddUTF8Char( unsigned char stemp ) {
	INFUNC( XCUNIPARSE::AddUTF8Char, NULL );

	// Normal ASCII character
	if (stemp < 0x7F) {
		// If we are in UTF8 mode, this is invalid
		if (unichars)
			RAISETOOLSERROR( 1 );

		univalue = stemp;
	} else if ((stemp & 0xC0) == 0xC0) {
		// This code signals the start of a utf8 sequence

		// If we are already in UTF8 mode, this is invalid
		if (unichars)
			RAISETOOLSERROR( 1 );

		// Grab the embedded value (6 bits)
		unsigned char tcount = stemp;
		univalue = (tcount & 0x7F);

		// Count the number of expected chars
		unichars = 1;
		tcount = tcount << 2;
		while (tcount & 0x80) {
			tcount = tcount << 1;
			unichars++;
		};
	} else {
		// This character is part of a utf8 sequence

		// We must be in UTF8 mode, or this is invalid
		if (!unichars)
			RAISETOOLSERROR( 1 );

		unsigned char tcount = stemp;
		univalue <<= 6;
		univalue += (tcount & 0x7F);

		// This is part of a unicode character
		unichars--;
	};

	OUTFUNC();
};



void XCUNIPARSE::AddUTF16Char( unsigned short character ) {
	// TODO: Implement
};

int XCUNIPARSE::OutputUTF8( unsigned char* destination, int size ) {
	// Compute size requirement
	int bytes_needed = 1;

	if (univalue > 0x7F)
		bytes_needed++;
	if (univalue > 0x7FF)
		bytes_needed++;
	if (univalue > 0xFFFF)
		bytes_needed++;
	if (univalue > 0x1FFFFF)
		bytes_needed++;
	if (univalue > 0x3FFFFFF)
		bytes_needed++;
	if (univalue > 0x7FFFFFFF)
		bytes_needed++;

	return bytes_needed;
};

int XCUNIPARSE::OutputUTF16( unsigned short* destination, int size ) {
	*destination = univalue;
	return 1;
};






///
//
// Actual conversion functions
//
///
bool UTF8ToUnicode( unsigned char value, XCUNIPARSE& parse ) {
	// Handle UTF-8 data
	if (parse.unichars == 0) {
		//
		// Check for a UTF-8 value
		//
		long check = 0xC0;

		// Next determine how many unicode characters to expect
		while (check <= 0xFC && (value & check)==check) {
			parse.unichars++;
			check = (check >> 1) | 0x80;
		};

		// If we used all our bits, we have more than 6 characters, which is invalid
		// Also, Check for invalid continuation signal
		if (check > 0xFC || ((value & 0xC0) == 0x80))
			return false;

		// If we have a unicode character, grab the first bits of it
		if (parse.unichars)
			parse.univalue = ~(check | 0xFFFFFF00) & value;
		else
			parse.univalue = value;
	} else {
		// We are creating a unicode value...
		if ((value & 0xC0) && (value != 0xC0)) {
			parse.univalue = (parse.univalue << 6) | (value & 0x3F);
			parse.unichars--;
		} else
			return false;
	};

	return true;
};




int UTF8ToUnicode( const char* source, wchar_t* dest, int len ) {
	int resultlen = 0;

	// Init internal structures
	XCUNIPARSE parse;
	memset( &parse, 0, sizeof( XCUNIPARSE ) );

	// While we have stuff to do and have room
	while (source && *source && resultlen < len) {
		if (!UTF8ToUnicode( *source, parse ))
			return -1;

		// Do we have a character?
		if (parse.unichars == 0) {
			resultlen++;
			*dest++ = parse.univalue;
		};

		source++;
	};

	*dest = 0;

	if (source && *source)
		return -1;
	else
		return resultlen;
};


int UnicodeToUTF8( const wchar_t* source, char* dest, int len ) {
	const wchar_t* stemp = source;
	char* dtemp = dest;
	int resultlen = 0;

	while (*stemp && resultlen < len) {
		wchar_t value = *stemp;
		int bytes_needed = 1;

		if (value > 0x7F)
			bytes_needed++;
		if (value > 0x7FF)
			bytes_needed++;
		if (value > 0xFFFF)
			bytes_needed++;
		if (value > 0x1FFFFF)
			bytes_needed++;
		if (value > 0x3FFFFFF)
			bytes_needed++;
		if (value > 0x7FFFFFFF)
			bytes_needed++;

		if (resultlen + bytes_needed < len) {
			resultlen += bytes_needed;
			if (dest) {
				// Write the character to the buffer
				if (bytes_needed == 1) {
					// We have an ASCII char (or not unicode mode)
					*dtemp = value;
					dtemp++;
				} else {
					// We have a UTF-8 encoding
					char* utemp = dtemp + bytes_needed;
					int mod = 0;
					while (value) {
						utemp--;
						*utemp = (value & 0x3F) | 0x80;

						value = value >> 6;
						mod = (mod >> 1) | 0x80;
					};
					*utemp |= mod;
					dtemp += bytes_needed;
				};
			};
		} else
			resultlen = -1;

		stemp++;
	};

	// NULL terminate
	*dtemp = 0;

	return resultlen;
};



int UnicodeToUTF8( const unsigned short source, char* dest ) {
	unsigned short value = source;
	int bytes_needed = 1;
	unsigned char* dtemp = (unsigned char*)dest;

	if (value > 0x7F)
		bytes_needed++;
	if (value > 0x7FF)
		bytes_needed++;
	if (value > 0xFFFF)
		bytes_needed++;
	if (value > 0x1FFFFF)
		bytes_needed++;
	if (value > 0x3FFFFFF)
		bytes_needed++;
	if (value > 0x7FFFFFFF)
		bytes_needed++;

	if (dest) {
		// Write the character to the buffer
		if (bytes_needed == 1) {
			// We have an ASCII char (or not unicode mode)
			*dtemp = value;
		} else {
			// We have a UTF-8 encoding
			unsigned char* utemp = dtemp + bytes_needed;
			int mod = 0;
			while (value) {
				utemp--;
				*utemp = (value & 0x3F) | 0x80;

				value = value >> 6;
				mod = (mod >> 1) | 0x80;
			};
			*utemp |= mod;
		};
	};
	return bytes_needed;
};



bool UTF8ToUTF8( const char* source, char* dest, int allocsize, int charsize ) {
	INFUNC( UTF8ToUTF8, NULL );

	const unsigned char* stemp = (unsigned char*)source;
	unsigned char* dtemp = (unsigned char*)dest;
	unsigned char* lastuni = NULL;
	int charsleft = charsize;
	int bytesleft = allocsize;
	long unichars = 0;

	// Go through source string, copying data over while we still have space, characters
	// left.  If we have to truncate, trunc at the least unresolved unicode character so
	// that we don't have bad utf8 encoding
	*dest = 0;
	while (stemp && *stemp && charsleft && bytesleft) {
//		printf( "Current: chars left=%3d, bytes left=%3d  String:%s  value:%x  unichars:%d\n", charsleft, bytesleft, dest, *stemp, unichars );

		bytesleft--;

		// Normal ASCII character
		if (*stemp < 0x7F) {
			// If we are in UTF8 mode, this is invalid
			if (unichars)
				RAISETOOLSERROR( 1 );

			lastuni = NULL;
			charsleft--;
		} else if ((*stemp & 0xC0) == 0xC0) {
			// If we are already in UTF8 mode, this is invalid
			if (unichars)
				RAISETOOLSERROR( 1 );

			// This signals the start of a UTF8 character
			lastuni = dtemp;

			// Count the number of expected chars
			unichars = 1;
			unsigned char tcount = *stemp;
			tcount = tcount << 2;
			while (tcount & 0x80) {
				tcount = tcount << 1;
				unichars++;
			};
		} else {
			// We must be in UTF8 mode, or this is invalid
			if (!unichars)
				RAISETOOLSERROR( 1 );

			// This is part of a unicode character
			unichars--;
			if (!unichars) {
				lastuni = NULL;
				charsleft--;
			};
		};

		*dtemp = *stemp;
		dtemp++;
		stemp++;
	};

	// NULL terminate
	bool result = true;

	if (lastuni) {
		*lastuni = 0;
		result = false;
	} else {
		*dtemp = 0;

		if (stemp && *stemp)
			result = false;
	};

//	printf( "  RESULT = %s\n", dest );
	OUTFUNCRET( result );
};



bool UTF8ToUTF16( const char* source, unsigned short* dest, int charsize ) {
	INFUNC( UTF8ToUTF16, NULL );

	const unsigned char* stemp = (unsigned char*)source;
	unsigned short* lastuni = dest;		// Store start location of last utf8 sequence (to handle truncation)
	int charsleft = charsize;
	XCUNIPARSE uniparse;
	memset( &uniparse, 0, sizeof( XCUNIPARSE ) );

	// Go through source string, copying data over while we still have space, characters
	// left.  If we have to truncate, trunc at the least unresolved unicode character so
	// that we don't have bad utf8 encoding
	*dest = 0;
	while (stemp && *stemp && charsleft) {
//		printf( "Current: chars left=%3d, bytes left=%3d  String:%s  value:%x  unichars:%d\n", charsleft, bytesleft, dest, *stemp, unichars );

		uniparse.AddUTF8Char( *stemp );
		if (!uniparse.unichars) {
			// We have a valid unicode character
			dest += uniparse.OutputUTF16( dest, charsleft );
			uniparse.Reset();
			charsleft--;
		};

		stemp++;
	};

	// NULL terminate
	bool result = true;

	if (uniparse.unichars) {
		*lastuni = 0;
		result = false;
	} else {
		*dest = 0;

		if (stemp && *stemp)
			result = false;
	};

//	printf( "  RESULT = %s\n", dest );
	OUTFUNCRET( result );
};








UniTranslator::UniTranslator() {
	utf8 = NULL;
	utf16 = NULL;

	utf8allocated = false;
	utf16allocated = false;
};

UniTranslator::UniTranslator( const unsigned short* value ) {
	utf16 = (unsigned short*)value;
	utf8 = NULL;

	utf8allocated = false;
	utf16allocated = false;
};

UniTranslator::UniTranslator( const char* value ) {
	utf16 = NULL;
	utf8 = (char*)value;

	utf8allocated = false;
	utf16allocated = false;
};

void UniTranslator::Clear() {
	if (utf8allocated) {
		delete [] utf8;
		utf8 = NULL;
	};

	if (utf16allocated) {
		delete [] utf16;
		utf16 = NULL;
	};
};

UniTranslator::~UniTranslator() {
	Clear();
};



UniTranslator& UniTranslator::operator=(const char* value) {

	Clear();



	utf16 = NULL;
	utf8 = (char*)value;


	utf8allocated = false;
	utf16allocated = false;


	return *this;

};


UniTranslator& UniTranslator::operator=(const unsigned short* value) {

	utf16 = (unsigned short*)value;
	utf8 = NULL;


	utf8allocated = false;
	utf16allocated = false;


	return *this;

};


const char* UniTranslator::UTF8() {
	if (!utf8) {
		// Count string size
		const unsigned short* temp = utf16;
		int size = 0;
		while (temp && *temp) {
			size += UnicodeToUTF8( *temp, NULL );
			temp++;
		};

		// Allocate string space
		utf8 = new char[ size+1 ];

		// Convert
		char* dest = utf8;
		temp = utf16;
		while (temp && *temp) {
			unsigned short value = *temp;
			dest += UnicodeToUTF8( value, dest );
			temp++;
		};
		*dest = 0;

		utf8allocated = true;
	};

	return utf8;
};


const unsigned short* UniTranslator::UTF16() {
	if (!utf16) {
		int len = strlen( utf8 );
		utf16 = new XMLCh[ len+1 ];
        XMLString::transcode(utf8, utf16, len);

		utf16allocated = true;

/*
		utf16 = new unsigned short[ len+1 ];
		UTF8ToUTF16( utf8, utf16, len );
		unsigned short* temp = utf16;

		printf( "UTF16 Result='" );
		while (*temp) {
			printf( "%4.4x", *temp );
			temp++;
		};
		printf( "'\n" );
		utf16allocated = true;
*/
	};
	return utf16;
};



__EXPORT ostream& operator<<(ostream& target, UniTranslator& toDump)

{

    target << toDump.UTF8();

    return target;

}

